﻿using BasicFramework.Services.MeshLoader.Nodes;
using SlimDX;
using System;
using System.Collections.Generic;

namespace BasicFramework.Services.MeshLoader.Meshes
{
    public class MeshContainer
    {
        public String Name;
        public string BasePath;
        public Vector3 Position;
        public List<String> Textures = new List<String>();
        public List<EffectNode> Materials = new List<EffectNode>();
        public List<SkinnedTangentVertex> Vertices = new List<SkinnedTangentVertex>();
        public ushort[] Indices;


        // Helpers to remember how we can reuse vertices for OptimizeVertexBuffer.
        public int[] ReuseVertexPositions;
        // Reverse reuse vertex positions, this one is even more important because
        // this way we can get a list of used vertices for a shared vertex pos.
        public List<int>[] ReverseReuseVertexPositions;


        public int NumOfTriangles = 0;
        public int NumOfVertices = 0;
        public int NumOfIndices = 0;

        // Object matrix for our mesh. Often used to fix mesh to bone skeleton.
        public Matrix ObjectMatrix = Matrix.Identity;


        public MeshContainer()
        {
        }

        #region Optimize vertex buffer
        #region Flip index order
        /// <summary>
        /// Little helper method to flip indices from 0, 1, 2 to 0, 2, 1.
        /// This way we can render with CullClockwiseFace.
        /// </summary>
        private int FlipIndexOrder(int oldIndex)
        {
            int polygonIndex = oldIndex % 3;
            if (polygonIndex == 0)
                return oldIndex;
            else if (polygonIndex == 1)
                return (ushort)(oldIndex + 1);
            else //if (polygonIndex == 2)
                return (ushort)(oldIndex - 1);
        }
        #endregion

        #region OptimizeVertexBuffer
        private ushort[] OptimizeVertexBuffer()
        {
            List<SkinnedTangentVertex> newVertices = new List<SkinnedTangentVertex>();
            List<ushort> newIndices = new List<ushort>();

            // Helper to only search already added newVertices and for checking the
            // old position indices by transforming them into newVertices indices.
            List<int> newVerticesPositions = new List<int>();

            // Go over all vertices (indices are currently 1:1 with the vertices)
            for (int num = 0; num < Vertices.Count; num++)
            {
                // Get current vertex
                SkinnedTangentVertex currentVertex = Vertices[num];
                bool reusedExistingVertex = false;

                // Find out which position index was used, then we can compare
                // all other vertices that share this position. They will not
                // all be equal, but some of them can be merged.
                int sharedPos = ReuseVertexPositions[num];
                foreach (int otherVertexIndex in ReverseReuseVertexPositions[sharedPos])
                {
                    // Only check the indices that have already been added!
                    if (otherVertexIndex != num &&
                        // Make sure we already are that far in our new index list
                      otherVertexIndex < newIndices.Count &&
                        // And make sure this index has been added to newVertices yet!
                      newIndices[otherVertexIndex] < newVertices.Count &&
                        // Then finally compare vertices (this call is slow, but thanks to
                        // all the other optimizations we don't have to call it that often)
                      SkinnedTangentVertex.NearlyEquals(currentVertex, newVertices[newIndices[otherVertexIndex]]))
                    {
                        // Reuse the existing vertex, don't add it again, just
                        // add another index for it!
                        newIndices.Add((ushort)newIndices[otherVertexIndex]);
                        reusedExistingVertex = true;
                        break;
                    }
                }

                if (reusedExistingVertex == false)
                {
                    // Add the currentVertex and set it as the current index
                    newIndices.Add((ushort)newVertices.Count);
                    newVertices.Add(currentVertex);
                }
            }

            // Finally flip order of all triangles to allow us rendering
            // with CullCounterClockwiseFace (default for XNA) because all the data
            // is in CullClockwiseFace format right now!
            for (int num = 0; num < newIndices.Count / 3; num++)
            {
                ushort swap = newIndices[num * 3 + 1];
                newIndices[num * 3 + 1] = newIndices[num * 3 + 2];
                newIndices[num * 3 + 2] = swap;
            } // for

            Vertices = newVertices; //reassign the vertex list with the optimized list

            return newIndices.ToArray();
        }
        #endregion

        #endregion

        #region Generate vertex and index buffers
        /// <summary>
        /// Generate vertex and index buffers
        /// </summary>
        public void GenerateVertexAndIndexBuffers()
        {
            // Optimize vertices first and build index buffer from that
            Indices = OptimizeVertexBuffer();

            NumOfVertices = Vertices.Count;

            if (Vertices.Count > ushort.MaxValue)
                throw new InvalidOperationException(
                  "Too much vertices to index, optimize vertices or use " +
                  "fewer vertices. Vertices=" + Vertices.Count +
                  ", Max Vertices for Index Buffer=" + ushort.MaxValue);

            NumOfIndices = Indices.Length;
        }
        #endregion
    }
}
